// Copyright 2012 Rui Ueyama. Released under the MIT license.

/*
 * 8cc C编译器主程序
 * 这是一个完整的C语言编译器，支持C11标准的大部分特性
 * 编译过程包括：词法分析 -> 语法分析 -> 代码生成 -> 汇编 -> 链接
 */

#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "8cc.h"

// 全局变量定义
static char *infile;      // 输入的C源文件名
static char *outfile;     // 输出的目标文件名
static char *asmfile;     // 中间生成的汇编文件名
static bool dumpast;      // 是否输出抽象语法树(AST)
static bool cpponly;      // 是否只进行预处理(不进行编译)
static bool dumpasm;      // 是否输出汇编代码(停止在汇编阶段)
static bool dontlink;     // 是否不进行链接(生成目标文件而非可执行文件)
static Buffer *cppdefs;   // 存储命令行定义的宏的缓冲区
static Vector *tmpfiles = &EMPTY_VECTOR; // 临时文件列表，程序退出时清理

/*
 * 显示使用帮助信息
 * @param exitcode 程序退出码，0表示正常退出，非0表示错误退出
 */
static void usage(int exitcode) {
    fprintf(exitcode ? stderr : stdout,
            "Usage: 8cc [ -E ][ -a ] [ -h ] <file>\n\n"
            "\n"
            "  -I<path>          添加头文件搜索路径\n"
            "  -E                只进行预处理并输出结果\n"
            "  -D name           定义宏name\n"
            "  -D name=def\n"
            "  -S                编译到汇编阶段停止(默认)\n"
            "  -c                不进行链接(默认)\n"
            "  -U name           取消定义宏name\n"
            "  -fdump-ast        输出抽象语法树\n"
            "  -fdump-stack      输出函数调用栈跟踪\n"
            "  -fno-dump-source  不在汇编代码中包含源码注释\n"
            "  -o filename       指定输出文件名\n"
            "  -g                当前无作用(为调试信息预留)\n"
            "  -Wall             启用所有警告\n"
            "  -Werror           将警告视为错误\n"
            "  -O<number>        当前无作用(为优化选项预留)\n"
            "  -m64              生成64位代码(默认)\n"
            "  -w                禁用所有警告\n"
            "  -h                显示此帮助信息\n"
            "\n"
            "必须指定 -a, -c, -E 或 -S 其中之一.\n\n");
    exit(exitcode);
}

/*
 * 删除临时文件
 * 程序退出时调用atexit注册的回调函数会执行此函数，清理生成的临时文件
 */
static void delete_temp_files() {
    // 遍历临时文件列表，逐一删除
    for (int i = 0; i < vec_len(tmpfiles); i++)
        unlink(vec_get(tmpfiles, i));
}

/*
 * 获取文件路径中的基本文件名(不含目录)
 * @param path 完整文件路径
 * @return 基本文件名
 */
static char *base(char *path) {
    return basename(strdup(path));
}

/*
 * 替换文件扩展名
 * @param filename 原文件名
 * @param suffix 新的扩展名字符
 * @return 替换扩展名后的新文件名
 */
static char *replace_suffix(char *filename, char suffix) {
    char *r = format("%s", filename);
    char *p = r + strlen(r) - 1;
    // 检查原文件是否以.c结尾
    if (*p != 'c')
        error("filename suffix is not .c");
    *p = suffix;  // 替换扩展名
    return r;
}

/*
 * 打开汇编文件用于写入
 * @return 文件指针
 */
static FILE *open_asmfile() {
    // 如果需要输出汇编代码，则使用指定的输出文件名或基于输入文件名生成
    if (dumpasm) {
        asmfile = outfile ? outfile : replace_suffix(base(infile), 's');
    } else {
        // 否则创建临时汇编文件
        asmfile = format("/tmp/8ccXXXXXX.s");
        if (!mkstemps(asmfile, 2))  // 创建临时文件
            perror("mkstemps");
        vec_push(tmpfiles, asmfile);  // 添加到临时文件列表
    }
    // 如果输出到标准输出
    if (!strcmp(asmfile, "-"))
        return stdout;
    // 打开文件用于写入
    FILE *fp = fopen(asmfile, "w");
    if (!fp)
        perror("fopen");
    return fp;
}

/*
 * 解析警告选项参数
 * @param s 警告选项字符串
 */
static void parse_warnings_arg(char *s) {
    if (!strcmp(s, "error"))
        warning_is_error = true;  // 将警告视为错误
    else if (strcmp(s, "all"))
        error("unknown -W option: %s", s);  // 未知的-W选项
}

/*
 * 解析-f开头的选项参数
 * @param s 选项字符串
 */
static void parse_f_arg(char *s) {
    if (!strcmp(s, "dump-ast"))
        dumpast = true;  // 输出抽象语法树
    else if (!strcmp(s, "dump-stack"))
        dumpstack = true;  // 输出栈跟踪
    else if (!strcmp(s, "no-dump-source"))
        dumpsource = false;  // 不在汇编中包含源码注释
    else
        usage(1);  // 未知选项，显示帮助
}

/*
 * 解析-m开头的选项参数
 * @param s 选项字符串
 */
static void parse_m_arg(char *s) {
    if (strcmp(s, "64"))
        error("Only 64 is allowed for -m, but got %s", s);  // 只支持64位
}

/*
 * 解析命令行选项
 * @param argc 参数个数
 * @param argv 参数数组
 */
static void parseopt(int argc, char **argv) {
    cppdefs = make_buffer();  // 创建宏定义缓冲区
    for (;;) {
        // 使用getopt解析命令行选项
        int opt = getopt(argc, argv, "I:ED:O:SU:W:acd:f:gm:o:hw");
        if (opt == -1)
            break;
        switch (opt) {
        case 'I': add_include_path(optarg); break;  // 添加头文件搜索路径
        case 'E': cpponly = true; break;  // 只进行预处理
        case 'D': {  // 定义宏
            char *p = strchr(optarg, '=');
            if (p)
                *p = ' ';  // 将=替换为空格，便于预处理器处理
            buf_printf(cppdefs, "#define %s\n", optarg);
            break;
        }
        case 'O': break;  // 优化选项(当前无作用)
        case 'S': dumpasm = true; break;  // 输出汇编代码
        case 'U':  // 取消定义宏
            buf_printf(cppdefs, "#undef %s\n", optarg);
            break;
        case 'W': parse_warnings_arg(optarg); break;  // 警告选项
        case 'c': dontlink = true; break;  // 不进行链接
        case 'f': parse_f_arg(optarg); break;  // -f选项
        case 'm': parse_m_arg(optarg); break;  // -m选项
        case 'g': break;  // 调试信息选项(当前无作用)
        case 'o': outfile = optarg; break;  // 指定输出文件
        case 'w': enable_warning = false; break;  // 禁用警告
        case 'h':
            usage(0);  // 显示帮助
        default:
            usage(1);  // 未知选项
        }
    }
    // 检查是否提供了输入文件
    if (optind != argc - 1)
        usage(1);

    // 检查是否指定了必要的选项之一
    if (!dumpast && !cpponly && !dumpasm && !dontlink)
        error("One of -a, -c, -E or -S must be specified");
    infile = argv[optind];  // 保存输入文件名
}

/*
 * 获取基础文件名
 * @return 输入文件名
 */
char *get_base_file() {
    return infile;
}

/*
 * 预处理阶段：只进行预处理并输出结果
 */
static void preprocess() {
    for (;;) {
        Token *tok = read_token();  // 读取下一个令牌
        if (tok->kind == TEOF)  // 文件结束
            break;
        if (tok->bol)  // 如果是行首
            printf("\n");
        if (tok->space)  // 如果前面有空格
            printf(" ");
        printf("%s", tok2s(tok));  // 输出令牌的字符串表示
    }
    printf("\n");
    exit(0);  // 预处理完成，退出程序
}

/*
 * 主函数：编译器入口点
 * @param argc 命令行参数数量
 * @param argv 命令行参数数组
 * @return 程序退出码
 */
int main(int argc, char **argv) {
    setbuf(stdout, NULL);  // 设置标准输出无缓冲
    // 注册程序退出时的清理函数
    if (atexit(delete_temp_files))
        perror("atexit");
    
    // 解析命令行选项
    parseopt(argc, argv);
    
    // 初始化词法分析器，读取输入文件
    lex_init(infile);
    
    // 初始化预处理器
    cpp_init();
    
    // 初始化语法分析器
    parse_init();
    
    // 设置汇编代码输出文件
    set_output_file(open_asmfile());
    
    // 如果有命令行定义的宏，则先处理这些宏定义
    if (buf_len(cppdefs) > 0)
        read_from_string(buf_body(cppdefs));

    // 如果只进行预处理，则执行预处理并退出
    if (cpponly)
        preprocess();

    // 语法分析阶段：读取顶层声明(函数、全局变量等)
    Vector *toplevels = read_toplevels();
    
    // 遍历所有顶层声明
    for (int i = 0; i < vec_len(toplevels); i++) {
        Node *v = vec_get(toplevels, i);
        if (dumpast)
            // 如果需要输出AST，则打印AST
            printf("%s", node2s(v));
        else
            // 否则生成汇编代码
            emit_toplevel(v);
    }

    // 关闭输出文件
    close_output_file();

    // 如果不是只输出AST或汇编代码，则继续后续处理
    if (!dumpast && !dumpasm) {
        // 如果没有指定输出文件名，则根据输入文件名生成目标文件名
        if (!outfile)
            outfile = replace_suffix(base(infile), 'o');
        
        // 创建子进程调用汇编器(as)将汇编代码编译为目标文件
        pid_t pid = fork();
        if (pid < 0) perror("fork");
        if (pid == 0) {
            // 在子进程中执行汇编器
            execlp("as", "as", "-o", outfile, "-c", asmfile, (char *)NULL);
            perror("execl failed");
        }
        // 等待汇编器执行完成
        int status;
        waitpid(pid, &status, 0);
        if (status < 0)
            error("as failed");  // 汇编失败
    }
    return 0;  // 编译成功完成
}